/**
 * This file is part of Logisim-evolution.
 *
 * Logisim-evolution is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Logisim-evolution is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with Logisim-evolution.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Original code by Carl Burch (http://www.cburch.com), 2011.
 * Subsequent modifications by:
 *   + Haute École Spécialisée Bernoise
 *     http://www.bfh.ch
 *   + Haute École du paysage, d'ingénierie et d'architecture de Genève
 *     http://hepia.hesge.ch/
 *   + Haute École d'Ingénierie et de Gestion du Canton de Vaud
 *     http://www.heig-vd.ch/
 *   + REDS Institute - HEIG-VD, Yverdon-les-Bains, Switzerland
 *     http://reds.heig-vd.ch
 * This version of the project is currently maintained by:
 *   + Kevin Walsh (kwalsh@holycross.edu, http://mathcs.holycross.edu/~kwalsh)
 */

package com.cburch.logisim.prefs;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.prefs.BackingStoreException;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;

import javax.swing.JFrame;

import com.cburch.logisim.Main;
import com.cburch.logisim.circuit.RadixOption;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.gui.start.Startup;
import com.cburch.logisim.util.LocaleListener;
import com.cburch.logisim.util.LocaleManager;
import com.cburch.logisim.util.PropertyChangeWeakSupport;

public class AppPreferences {

  public static PropertyChangeWeakSupport.Producer propertyChangeProducer = new PropertyChangeWeakSupport.Producer() {
    PropertyChangeWeakSupport propListeners = new PropertyChangeWeakSupport(AppPreferences.class);

    public PropertyChangeWeakSupport getPropertyChangeListeners() {
      return propListeners;
    }
  };

  private static class LocalePreference extends PrefMonitor<String> {
    private static Locale findLocale(String lang) {
      Locale[] check;
      for (int set = 0; set < 2; set++) {
        if (set == 0)
          check = new Locale[] { Locale.getDefault(), Locale.ENGLISH };
        else
          check = Locale.getAvailableLocales();
        for (int i = 0; i < check.length; i++) {
          Locale loc = check[i];
          if (loc != null && loc.getLanguage().equals(lang))
            return loc;
        }
      }
      return null;
    }

    public LocalePreference() {
      super("locale", "");
      if (value != null && !value.equals(""))
        LocaleManager.setLocale(new Locale(value));
      LocaleManager.addLocaleListener(myListener);
      myListener.localeChanged();
    }

    @Override
    public void set(String value) {
      if (findLocale(value) != null)
        super.set(value);
    }
  }

  //
  // methods for accessing preferences
  //
  private static class MyListener
      implements PreferenceChangeListener, LocaleListener {
    public void localeChanged() {
      Locale loc = LocaleManager.getLocale();
      String lang = loc.getLanguage();
      if (LOCALE != null) {
        LOCALE.set(lang);
      }
    }

    public void preferenceChange(PreferenceChangeEvent event) {
      Preferences prefs = event.getNode();
      String prop = event.getKey();
      if (ACCENTS_REPLACE.getIdentifier().equals(prop)) {
        getPrefs();
        LocaleManager.setReplaceAccents(ACCENTS_REPLACE.get());
      } else if (prop.equals(TEMPLATE_TYPE)) {
        int oldValue = templateType;
        int value = prefs.getInt(TEMPLATE_TYPE, TEMPLATE_UNKNOWN);
        if (value != oldValue) {
          templateType = value;
          propertyChangeProducer.firePropertyChange(TEMPLATE, oldValue, value);
          propertyChangeProducer.firePropertyChange(TEMPLATE_TYPE, oldValue, value);
        }
      } else if (prop.equals(TEMPLATE_FILE)) {
        File oldValue = templateFile;
        File value = convertFile(prefs.get(TEMPLATE_FILE, null));
        if (value == null ? oldValue != null : !value.equals(oldValue)) {
          templateFile = value;
          if (templateType == TEMPLATE_CUSTOM) {
            customTemplate = null;
            propertyChangeProducer.firePropertyChange(TEMPLATE, oldValue, value);
          }
          propertyChangeProducer.firePropertyChange(TEMPLATE_FILE, oldValue, value);
        }
      }
    }
  }

  public static void clear() {
    Preferences p = getPrefs(true);
    try {
      p.clear();
    } catch (BackingStoreException e) {
    }
  }

  private static File convertFile(String fileName) {
    if (fileName == null || fileName.equals("")) {
      return null;
    } else {
      File file = new File(fileName);
      return file.canRead() ? file : null;
    }
  }

  private static Template getCustomTemplate() {
    File toRead = templateFile;
    if (customTemplateFile == null || !(customTemplateFile.equals(toRead))) {
      if (toRead == null) {
        customTemplate = null;
        customTemplateFile = null;
      } else {
        FileInputStream reader = null;
        try {
          reader = new FileInputStream(toRead);
          customTemplate = Template.create(reader);
          customTemplateFile = templateFile;
        } catch (Exception t) {
          setTemplateFile(null);
          customTemplate = null;
          customTemplateFile = null;
        } finally {
          if (reader != null) {
            try {
              reader.close();
            } catch (IOException e) {
            }
          }
        }
      }
    }
    return customTemplate == null ? getPlainTemplate() : customTemplate;
  }

  public static Template getEmptyTemplate() {
    if (emptyTemplate == null)
      emptyTemplate = Template.createEmpty();
    return emptyTemplate;
  }

  private static Template getPlainTemplate() {
    if (plainTemplate == null) {
      ClassLoader ld = Startup.class.getClassLoader();
      InputStream in = ld.getResourceAsStream("resources/logisim/default.templ");
      if (in == null) {
        plainTemplate = getEmptyTemplate();
      } else {
        try {
          try {
            plainTemplate = Template.create(in);
          } finally {
            in.close();
          }
        } catch (Exception e) {
          plainTemplate = getEmptyTemplate();
        }
      }
    }
    return plainTemplate;
  }

  public static Preferences getPrefs() {
    return getPrefs(false);
  }

  private static Preferences getPrefs(boolean shouldClear) {
    if (prefs == null) {
      synchronized (AppPreferences.class) {
        if (prefs == null) {
          Preferences p = Preferences.userNodeForPackage(Main.class);
          if (shouldClear) {
            try {
              p.clear();
            } catch (BackingStoreException e) {
            }
          }
          myListener = new MyListener();
          p.addPreferenceChangeListener(myListener);
          prefs = p;

          setTemplateFile(convertFile(p.get(TEMPLATE_FILE, null)));
          setTemplateType(p.getInt(TEMPLATE_TYPE, TEMPLATE_PLAIN));
        }
      }
    }
    return prefs;
  }

  //
  // recent projects
  //
  public static List<File> getRecentFiles() {
    return recentProjects.getRecentFiles();
  }

  //
  // template methods
  //
  public static Template getTemplate() {
    getPrefs();
    switch (templateType) {
      case TEMPLATE_PLAIN:
        return getPlainTemplate();
      case TEMPLATE_EMPTY:
        return getEmptyTemplate();
      case TEMPLATE_CUSTOM:
        return getCustomTemplate();
      default:
        return getPlainTemplate();
    }
  }

  public static File getTemplateFile() {
    getPrefs();
    return templateFile;
  }

  //
  // accessor methods
  //
  public static int getTemplateType() {
    getPrefs();
    int ret = templateType;
    if (ret == TEMPLATE_CUSTOM && templateFile == null) {
      ret = TEMPLATE_UNKNOWN;
    }
    return ret;
  }

  public static void handleGraphicsAcceleration() {
    String accel = GRAPHICS_ACCELERATION.get();
    try {
      if (accel == ACCEL_NONE) {
        System.setProperty("sun.java2d.opengl", "False");
        System.setProperty("sun.java2d.d3d", "False");
      } else if (accel == ACCEL_OPENGL) {
        System.setProperty("sun.java2d.opengl", "True");
        System.setProperty("sun.java2d.d3d", "False");
      } else if (accel == ACCEL_D3D) {
        System.setProperty("sun.java2d.opengl", "False");
        System.setProperty("sun.java2d.d3d", "True");
      }
    } catch (Exception t) {
    }
  }

  public static void setTemplateFile(File value) {
    getPrefs();
    setTemplateFile(value, null);
  }

  public static void setTemplateFile(File value, Template template) {
    getPrefs();
    if (value != null && !value.canRead())
      value = null;
    if (value == null ? templateFile != null : !value.equals(templateFile)) {
      try {
        customTemplateFile = template == null ? null : value;
        customTemplate = template;
        getPrefs().put(TEMPLATE_FILE,
            value == null ? "" : value.getCanonicalPath());
      } catch (IOException ex) {
      }
    }
  }

  public static void setTemplateType(int value) {
    getPrefs();
    if (value != TEMPLATE_PLAIN && value != TEMPLATE_EMPTY
        && value != TEMPLATE_CUSTOM) {
      value = TEMPLATE_UNKNOWN;
    }
    if (value != TEMPLATE_UNKNOWN && templateType != value) {
      getPrefs().putInt(TEMPLATE_TYPE, value);
    }
  }

  public static void updateRecentFile(File file) {
    recentProjects.updateRecent(file);
  }

  // class variables for maintaining consistency between properties,
  // internal variables, and other classes
  private static Preferences prefs = null;

  private static MyListener myListener = null;

  // Template preferences
  public static final int TEMPLATE_UNKNOWN = -1;
  public static final int TEMPLATE_EMPTY = 0;
  public static final int TEMPLATE_PLAIN = 1;
  public static final int TEMPLATE_CUSTOM = 2;
  public static final String TEMPLATE = "template";
  public static final String TEMPLATE_TYPE = "templateType";
  public static final String TEMPLATE_FILE = "templateFile";
  private static int templateType = TEMPLATE_PLAIN;

  private static File templateFile = null;

  private static Template plainTemplate = null;
  private static Template emptyTemplate = null;
  private static Template customTemplate = null;
  private static File customTemplateFile = null;

  // International preferences
  public static final String SHAPE_SHAPED = "shaped";
  public static final String SHAPE_RECTANGULAR = "rectangular";
  public static final String SHAPE_DIN40700 = "din40700";

  public static final PrefMonitor<String> GATE_SHAPE = new PrefMonitor<>("gateShape",
      new String[] { SHAPE_SHAPED, SHAPE_RECTANGULAR, SHAPE_DIN40700 },
      SHAPE_SHAPED);
  public static final PrefMonitor<String> LOCALE = new LocalePreference();
  public static final PrefMonitor<Boolean> ACCENTS_REPLACE = new PrefMonitor<>("accentsReplace", false);
  // Window preferences
  public static final PrefMonitor<Boolean> SHOW_TICK_RATE = new PrefMonitor<>("showTickRate", false);
  public static final PrefMonitor<Boolean> SHOW_COORDS = new PrefMonitor<>("showCoordinates", false);
  public static final String TOOLBAR_HIDDEN = "hidden";
  public static final String TOOLBAR_DOWN_MIDDLE = "downMiddle";
  public static final PrefMonitor<String> TOOLBAR_PLACEMENT = new PrefMonitor<>("toolbarPlacement",
      new String[] {
          Direction.NORTH.toString(), Direction.SOUTH.toString(),
          Direction.EAST.toString(), Direction.WEST.toString(),
          TOOLBAR_DOWN_MIDDLE, TOOLBAR_HIDDEN },
      Direction.NORTH.toString());
  // Layout preferences
  public static final PrefMonitor<Boolean> PRINTER_VIEW = new PrefMonitor<>("printerView", false);
  public static final PrefMonitor<Boolean> ATTRIBUTE_HALO = new PrefMonitor<>("attributeHalo", true);
  public static final PrefMonitor<Boolean> COMPONENT_TIPS = new PrefMonitor<>("componentTips", true);
  public static final PrefMonitor<Boolean> MOVE_KEEP_CONNECT = new PrefMonitor<>("keepConnected", true);
  public static final PrefMonitor<Boolean> ADD_SHOW_GHOSTS = new PrefMonitor<>("showGhosts", true);
  public static final String ADD_AFTER_UNCHANGED = "unchanged";
  public static final String ADD_AFTER_EDIT = "edit";
  public static final PrefMonitor<String> ADD_AFTER = new PrefMonitor<>("afterAdd",
      new String[] { ADD_AFTER_EDIT, ADD_AFTER_UNCHANGED },
      ADD_AFTER_EDIT);

  public static final PrefMonitor<String> POKE_WIRE_RADIX1;
  public static final PrefMonitor<String> POKE_WIRE_RADIX2;
  static {
    RadixOption[] radixOptions = RadixOption.OPTIONS;
    String[] radixStrings = new String[radixOptions.length];
    for (int i = 0; i < radixOptions.length; i++) {
      radixStrings[i] = radixOptions[i].getSaveString();
    }
    POKE_WIRE_RADIX1 = new PrefMonitor<>("pokeRadix1",
        radixStrings, RadixOption.RADIX_2.getSaveString());
    POKE_WIRE_RADIX2 = new PrefMonitor<>("pokeRadix2",
        radixStrings, RadixOption.RADIX_10_SIGNED.getSaveString());
  }

  // Experimental preferences
  public static final String ACCEL_DEFAULT = "default";
  public static final String ACCEL_NONE = "none";
  public static final String ACCEL_OPENGL = "opengl";
  public static final String ACCEL_D3D = "d3d";
  public static final PrefMonitor<String> GRAPHICS_ACCELERATION = new PrefMonitor<>("graphicsAcceleration",
      new String[] { ACCEL_DEFAULT, ACCEL_NONE, ACCEL_OPENGL, ACCEL_D3D },
      ACCEL_DEFAULT);
  public static final String DUALSCREEN_NONE = "none";
  public static final String DUALSCREEN_FIX = "fixBlackWindows";
  public static final String DUALSCREEN_MORE = "fixBlackWindowsMore";
  public static final String DUALSCREEN_MOST = "fixBlackWindowsBest";
  public static final PrefMonitor<String> DUALSCREEN = new PrefMonitor<>("dualScreenFixes",
      new String[] { DUALSCREEN_NONE, DUALSCREEN_FIX, DUALSCREEN_MORE, DUALSCREEN_MOST },
      DUALSCREEN_NONE);

  // Third party softwares preferences
  public static final PrefMonitor<String> QUESTA_PATH = new PrefMonitor<>("questaPath", "");
  public static final PrefMonitor<Boolean> QUESTA_VALIDATION = new PrefMonitor<>("questaValidation", false);

  // hidden window preferences - not part of the preferences dialog, changes
  // to preference does not affect current windows, and the values are not
  // saved until the application is closed
  public static final String RECENT_PROJECTS = "recentProjects";
  private static final RecentProjects recentProjects = new RecentProjects();

  public static final PrefMonitor<Double> TICK_FREQUENCY = new PrefMonitor<>("tickFrequency", 1.0);

  public static final PrefMonitor<Boolean> LAYOUT_SHOW_GRID = new PrefMonitor<>("layoutGrid", true);

  public static final PrefMonitor<Double> LAYOUT_ZOOM = new PrefMonitor<>("layoutZoom", 1.0);

  public static final PrefMonitor<Boolean> APPEARANCE_SHOW_GRID = new PrefMonitor<>("appearanceGrid", true);

  public static final PrefMonitor<Double> APPEARANCE_ZOOM = new PrefMonitor<>("appearanceZoom", 1.0);

  public static final PrefMonitor<Integer> WINDOW_STATE = new PrefMonitor<>("windowState", JFrame.NORMAL);

  public static final PrefMonitor<Integer> WINDOW_WIDTH = new PrefMonitor<>("windowWidth", 640);

  public static final PrefMonitor<Integer> WINDOW_HEIGHT = new PrefMonitor<>("windowHeight", 480);

  public static final PrefMonitor<String> WINDOW_LOCATION = new PrefMonitor<>("windowLocation", "0,0");

  public static final PrefMonitor<Double> WINDOW_MAIN_SPLIT = new PrefMonitor<>("windowMainSplit", 0.25);

  public static final PrefMonitor<Double> WINDOW_LEFT_SPLIT = new PrefMonitor<>("windowLeftSplit", 0.5);

  public static final PrefMonitor<Double> WINDOW_RIGHT_SPLIT = new PrefMonitor<>("windowRightSplit", 0.75);

  public static final PrefMonitor<String> DIALOG_DIRECTORY = new PrefMonitor<>("dialogDirectory", "");

  public static final PrefMonitor<Integer> WIRING_TOOL_TIP = new PrefMonitor<>("wiringToolTip", 3);

  public static final PrefMonitor<Integer> CUTTER_TOOL_TIP = new PrefMonitor<>("cutterToolTip", 3);
}
